package sub

import (
	"bufio"
	"encoding/base64"
	"encoding/binary"
	"fmt"
	gowin32wx "github.com/gorpher/gowin32"
	golnk "github.com/parsiya/golnk"
	uuid "github.com/satori/go.uuid"
	"github.com/urfave/cli/v2"
	"image/png"
	"io/fs"
	"lib/structs/cap"
	"lib/utils"
	"os"
	"path"
	"path/filepath"
	"strings"
)

func CollectInfoCommand() *cli.Command {
	var comm = &cli.Command{
		Name:      "info",
		Usage:     "信息采集",
		UsageText: fmt.Sprintf("eg: mccp info -sayHi "),
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:        "sayHi",
				Usage:       "获取举手信息",
				DefaultText: "",
			},
			&cli.StringFlag{
				Name:        "apps",
				Usage:       "获取云应用信息",
				DefaultText: "C:\\miao\\app\\",
			},
		},
	}
	comm.Action = func(ctx *cli.Context) error {
		if ctx.IsSet("sayHi") {
			msg, err := getHandsUpMetaInfo()
			if err != nil {
				return cli.Exit(fmt.Sprintf("获取举手信息时发生错误:[%v]", err), 1)
			}
			w := bufio.NewWriter(os.Stdout)
			_, _ = w.WriteString(msg)
			_ = w.Flush()
			return nil
		}
		if ctx.IsSet("apps") {
			fp := ctx.String("apps")
			msg, err := getAppList(fp)
			if err != nil {
				return cli.Exit(fmt.Sprintf("获取应用信息时发生错误:[%v]", err), 1)
			}
			w := bufio.NewWriter(os.Stdout)
			_, _ = w.WriteString(msg)
			_ = w.Flush()
			return nil
		}
		return nil
	}
	return comm
}

func getHandsUpMetaInfo() (str string, err error) {
	// todo 填充举手信息
	str = "{}"
	return
}

// getAppList 获取云应用信息
func getAppList(lnkPath string) (str string, err error) {
	apps := make([]cap.AppInfo, 0)
	err = filepath.Walk(lnkPath, func(fp string, info fs.FileInfo, err error) error {
		if strings.Trim(path.Ext(path.Base(fp)), ".") == "lnk" {
			var appInfo cap.AppInfo
			appInfo, err = getAppInfoByLnk(fp)
			if err == nil {
				apps = append(apps, appInfo)
			}
		}
		return nil
	})
	str = utils.Obj2Str(apps)
	return
}

// getAppInfoByLnk 通过快捷方式获取应用信息
func getAppInfoByLnk(lnkPath string) (appInfo cap.AppInfo, err error) {
	var flnk golnk.LnkFile
	flnk, err = golnk.File(lnkPath)
	if err != nil {
		err = fmt.Errorf("无效的lnk path:%s", lnkPath)
		return
	}
	var imgStr string
	if strings.HasSuffix(flnk.StringData.IconLocation, ".ico") {
		imgStr = icoToBase64(flnk.StringData.IconLocation)
	} else {
		imgStr = exeToBase64(flnk.LinkInfo.LocalBasePath)
	}
	linkName := filepath.Base(lnkPath)
	appName := strings.TrimSuffix(linkName, filepath.Ext(linkName))
	appInfo = cap.AppInfo{
		AppName:   appName,
		LinkPath:  lnkPath,
		ExePath:   flnk.LinkInfo.LocalBasePath,
		AppParams: flnk.StringData.CommandLineArguments,
		WorkSpace: flnk.StringData.WorkingDir,
		Icon:      imgStr,
		Remark:    flnk.StringData.NameString,
	}
	return
}

// icoToBase64 将ico中的第一个图像转换为png格式
func icoToBase64(iconPath string) (str string) {
	var (
		f   *os.File
		err error
	)
	f, err = os.Open(iconPath)
	if err != nil {
		return
	}
	defer f.Close()
	// 读取ICO文件头
	var header struct {
		Reserved uint16
		Type     uint16
		Count    uint16
	}
	if err = binary.Read(f, binary.LittleEndian, &header); err != nil {
		return
	}

	// 读取图标目录
	dirEntries := make([]struct {
		Width    uint8
		Height   uint8
		Colors   uint8
		Reserved uint8
		Planes   uint16
		BitCount uint16
		Size     uint32
		Offset   uint32
	}, header.Count)
	if err = binary.Read(f, binary.LittleEndian, &dirEntries); err != nil {
		return
	}
	if len(dirEntries) < 1 {
		return
	}

	// 读取第一个图标数据
	_, _ = f.Seek(int64(dirEntries[0].Offset), 0)
	icoData := make([]byte, dirEntries[0].Size)
	if _, err = f.Read(icoData); err != nil {
		err = fmt.Errorf("无法读取ICO图标数据:%s", err)
		return
	}
	// 这个png图片有可能是可以打开但是转换为base64后浏览器无法识别的情况
	// 可能是看图软件做了兼容，具体情况后面在研究
	execPath, err := os.Executable()
	if err != nil {
		return
	}
	pngImg := filepath.Join(filepath.Dir(execPath), uuid.NewV4().String()+".png")
	pngFile, err := os.Create(pngImg)
	if err != nil {
		err = fmt.Errorf("无法创建PNG文件:%s", err)
		return
	}
	defer pngFile.Close()
	if _, err = pngFile.Write(icoData); err != nil {
		err = fmt.Errorf("无法写入PNG文件:%s", err)
		return
	}
	str = "data:image/png;base64," + base64.StdEncoding.EncodeToString(icoData)
	return
}

// exeToBase64 通过 windows 提取exe的图像信息
func exeToBase64(execPath string) (str string) {
	img, err := gowin32wx.ExtractPrivateExtractIcons(execPath, 128, 128)
	if err != nil {
		return ""
	}
	fp, err := os.Create("output.png")
	defer func() {
		fp.Close()
		os.Remove("output.png")
	}()
	if err != nil {
		return ""
	}
	err = png.Encode(fp, img)
	if err != nil {
		return ""
	}
	b, err := os.ReadFile("output.png")
	if err != nil {
		return ""
	}
	str = "data:image/png;base64," + base64.StdEncoding.EncodeToString(b)
	return
}
